package plus.easydo.starter.oauth.client.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.util.StringUtils;
import plus.easydo.starter.oauth.api.TokenApi;
import plus.easydo.starter.oauth.core.config.SecurityBeanConfig;

import java.security.Principal;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * 自定义OAuth2RestTemplate
 *
 * @author yuzhanfeng
 */
public class CustomizeClientServer {

    Logger log = LoggerFactory.getLogger(CustomizeClientServer.class);
    private boolean isOauthServer;
    private String token = "";
    private OAuth2AccessToken oAuth2AccessToken;
    private final OAuth2ProtectedResourceDetails resource;
    private final OAuth2RestTemplate oAuth2RestTemplate;
    @Autowired(required = false)
    TokenApi tokenApi;
    @Qualifier("serverBeanConfig")
    @Autowired(required = false)
    private SecurityBeanConfig securityBeanConfig;


    public CustomizeClientServer(OAuth2ProtectedResourceDetails clientCredentialsResourceDetails) {
        log.info("初始化CustomizeClientServer");
        this.resource = clientCredentialsResourceDetails;
        this.oAuth2RestTemplate = new OAuth2RestTemplate(clientCredentialsResourceDetails);
    }

    public OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException {
        if (StringUtils.isEmpty(token)) {
            if (isOauthServer()) {
                return localServerGetAccessToken();
            } else {
                return remoteGetAccessToken();
            }
        } else {
            checkToken(token);
            return this.oAuth2AccessToken;
        }
    }

    /**
     * 判断是否为本地服务
     *
     * @return boolean
     * @author laoyu
     */
    private boolean isOauthServer() {
        this.isOauthServer = securityBeanConfig != null;
        return isOauthServer;
    }

    /**
     * 如果使用者为授权服务器本身，则尝试本地生成token
     *
     * @return org.springframework.security.oauth2.common.OAuth2AccessToken
     * @author laoyu
     */
    public OAuth2AccessToken localServerGetAccessToken() {
        AuthorizationServerTokenServices tokenService = securityBeanConfig.getAuthorizationServerTokenServices();
        ClientDetails client = securityBeanConfig.getClientDetailsService().loadClientByClientId(resource.getClientId());
        Collection<GrantedAuthority> authorities = client.getAuthorities();
        Set<String> resourceIds = client.getResourceIds();
        Set<String> scope = client.getScope();
        OAuth2Request request = new OAuth2Request(null, resource.getClientId(), authorities, true, scope, resourceIds, null, null, null);
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(request, null);
        OAuth2AccessToken accessToken = tokenService.getAccessToken(oAuth2Authentication);
        if (accessToken == null) {
            accessToken = tokenService.createAccessToken(oAuth2Authentication);
        }
        setToken(accessToken);
        log.info("localServerGetAccessToken()===> {}", this.token);
        return accessToken;
    }

    private void setToken(OAuth2AccessToken accessToken) {
        this.oAuth2AccessToken = accessToken;
        this.token = accessToken.getValue();
    }


    /**
     * 校验token
     *
     * @param token token
     * @author laoyu
     */
    public void checkToken(String token) {
        if (isOauthServer) {
            AuthorizationServerTokenServices tokenService = securityBeanConfig.getAuthorizationServerTokenServices();
            if (tokenService instanceof DefaultTokenServices) {
                DefaultTokenServices tokenServices = (DefaultTokenServices) tokenService;
                OAuth2AccessToken accToken = tokenServices.readAccessToken(token);
                if (accToken == null || accToken.isExpired()) {
                    log.error("checkToken()=>校验令牌失败：{},{}", token, "调用本地服务重新获取token");
                    localServerGetAccessToken();
                }
            }
        } else {
            if (Objects.nonNull(tokenApi)) {
                Map<String, Object> result = tokenApi.checkToken(this.token);
                log.info("tokenApi.checkToken(): {}", result);
                Object clientId = result.get(OAuth2Utils.CLIENT_ID);
                if (Objects.isNull(clientId)) {
                    remoteGetAccessToken();
                }
            }
            if (oAuth2AccessToken.isExpired()) {
                oAuth2RestTemplate.getOAuth2ClientContext().setAccessToken(null);
                remoteGetAccessToken();
            }
        }
    }

    /**
     * 远程获取token
     *
     * @return org.springframework.security.oauth2.common.OAuth2AccessToken
     * @author laoyu
     */
    public OAuth2AccessToken remoteGetAccessToken() {
        if (Objects.nonNull(tokenApi)) {
            OAuth2AccessToken accessToken = feignClientGetAccessToken();
            if (Objects.nonNull(accessToken)) {
                setToken(accessToken);
                return accessToken;
            }
        }
        OAuth2AccessToken accessToken = oAuth2RestTemplate.getAccessToken();
        setToken(accessToken);
        log.info("remoteGetAccessToken()===> {}", this.token);
        return accessToken;
    }

    /**
     * 通过openFeign调用服务获取token
     *
     * @return org.springframework.security.oauth2.common.OAuth2AccessToken
     * @author laoyu
     */
    public OAuth2AccessToken feignClientGetAccessToken() {
        HashMap<String, String> param = new HashMap<>(4);
        param.put(OAuth2Utils.GRANT_TYPE, resource.getGrantType());
        param.put(OAuth2Utils.CLIENT_ID, resource.getClientId());
        param.put("client_secret", resource.getClientSecret());
        List<GrantedAuthority> list = Collections.emptyList();
        Principal principal = new UsernamePasswordAuthenticationToken(new User(resource.getClientId(), "", list), null);
        ResponseEntity<OAuth2AccessToken> result = tokenApi.getAccessToken(principal, param);
        log.info("feignClientGetAccessToken()===> {}", result);
        return result.getBody();
    }

}
